#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <pthread.h>

#include "server.h"
#include "sockio.h"
#include "client.h"

#ifndef BACKLOG
#define BACKLOG 20
#endif

void client_create(client * clientp);
void client_destroy(server * serverp, client * clientp);
int client_quit(client * clientp);
int client_update(client * clientp);
client * drop(server *serverp, client * clientp);

int serverupdate(server *serverp)
{
  fd_set * fdset, * readset;
  int sfd;
  
  int status;
  size_t count, total;
  client * clientp = 0;
  int i = 0;
  
  unsigned char shall_drop = 0;
  
  /* espera meio segundo por dados*/
  static struct timeval tv = {0, 500};
  
  assert(serverp != NULL);
  assert(serverp->fd > 0);
  assert(FD_ISSET(serverp->fd, &(serverp->fdset)));
  
  sfd = serverp->fd; /* server fd */
  fdset = &(serverp->fdset);
  readset = &(serverp->readset);
  
  /* incrementa a contagem de frames */
  serverp->frame++;
  
  *readset = *fdset;
  
  do {
    status = select(serverp->fdmax+1, readset, NULL, NULL, &tv);
  } while (status < 0 && errno == EINTR);
  if (status < 0)
    goto error;
  
  /* checa se o servidor esta pronto para aceitar */
  if (FD_ISSET(sfd, readset))
  {
    /* aloca um novo cliente */
    clientp = calloc(1, sizeof(*clientp));
    clientp->id = sockaccept(sfd);
    if (clientp->id < 0) /* boom */
    {
      free(clientp);
      perror("Erro ao criar novo cliente");
    }
    else /* adiciona o novo cliente */
    {
      FD_SET(clientp->id, fdset);
      if (clientp->id > serverp->fdmax)
        serverp->fdmax = clientp->id;
      
      /* aloca os buffers desse cliente */
      client_create(clientp);
      
      /* adiciona o novo cliente no comeco da fila */
      clientp->next = serverp->clients;
      serverp->clients = clientp;
      serverp->clientcount++;
      clientp->apperture = serverp->bps;
      if (clientp->next) /* se ja houver um cliente, ajusta o <- (prev) */
        clientp->next->prev = clientp;
      
    }
  }
  
  
  /* itera por todos os clientes, checando se há writes a realizar */
  clientp = serverp->clients;
  i = 0;
  while (clientp != 0)
  {
    *readset = *fdset;
    shall_drop = 0;
    
    do {
      status = select(serverp->fdmax+1, readset, NULL, NULL, &tv);
    } while (status < 0 && errno == EINTR);
    if (status < 0)
      goto error;
    
    /* checa se o servidor esta pronto para aceitar */
    if (FD_ISSET(sfd, readset))
    {
      /* aloca um novo cliente */
      clientp = calloc(1, sizeof(*clientp));
      clientp->id = sockaccept(sfd);
      if (clientp->id < 0) /* boom */
      {
        free(clientp);
        perror("Erro ao criar novo cliente");
      }
      else /* adiciona o novo cliente */
      {
        FD_SET(clientp->id, fdset);
        if (clientp->id > serverp->fdmax)
          serverp->fdmax = clientp->id;
        
        /* aloca os buffers desse cliente */
        client_create(clientp);
        
        /* adiciona o novo cliente no comeco da fila */
        clientp->next = serverp->clients;
        serverp->clients = clientp;
        serverp->clientcount++;
        clientp->apperture = serverp->bps;
        if (clientp->next) /* se ja houver um cliente, ajusta o <- (prev) */
          clientp->next->prev = clientp;
      }
    }    
    
    /* cliente num frame diferente com a janela descarregada */
    if (clientp->frame != serverp->frame)
    {
      clientp->apperture = serverp->bps;
      clientp->frame = serverp->frame;
    }
    else if (clientp->apperture <= 0)
    {
      if (i >= serverp->clientcount)
        break; /* sai do loop se gastamos todo mundo */
      else
      {
        clientp = clientp->next;
        i++;
        continue; /* ve o proximo ai... */
      }
    }
    
    total = 0;
    
    /* update de cada cliente */
  
    if (serverp->update(clientp) > 0)
    {
      /* devemos dropar esse cliente */
      shall_drop = 1;
      goto shalldrop;
    }
    #ifdef USE_THREADS
//     printf("%s %d   lock mutex %x\n", __func__, __LINE__, &(clientp->lock));
    pthread_mutex_lock(&(clientp->lock));
    #endif  
    
    if (FD_ISSET(clientp->id, readset)) /* leitura pronta */
    {
      if (clientp->inputlen < clientp->inputsz)
      {  
        status = sockread(clientp->id, 
                clientp->input + clientp->inputlen,
                clientp->inputsz - clientp->inputlen);
        if (status > 0) /* soma os bytes lidos */
          clientp->inputlen += status;
        else
        {
          /* drope esse cliente */
          shall_drop = 1;
          goto shalldrop;
        }
      }
    }

    count = clientp->apperture;
    if ((count > (clientp->outputlen - clientp->sent)) || serverp->bps == 0)
      count = (clientp->outputlen - clientp->sent);
    
    /* ajusta para nao enviar demais */
    if (total + count > serverp->bps && serverp->bps > 0)
      count = serverp->bps - total;
    
    clientp->sent += sockwrite(clientp->id, clientp->output + clientp->sent,
                              count);
    total += clientp->sent;
    clientp->apperture -= clientp->sent;
    
    #ifdef USE_THREADS
//     printf("%s %d unlock mutex %x\n", __func__, __LINE__, &(clientp->lock));
    pthread_mutex_unlock(&(clientp->lock));
    #endif
    
    shalldrop:
      if (shall_drop)
        clientp = drop(serverp, clientp); // drop jah da unlock e destroy */
    
    if (clientp == 0 || (clientp->next == 0 /* nao deixa entrar no else abaixo no caso do ->next = 0 */))
      clientp = serverp->clients;
    else
      clientp = clientp->next;
  }
  return 0;
  
  /* em caso de erros irrecuperaveis */
  error:
  {
    if (errno)
      perror("Erro ao atualizar estado do servidor");
    serverdestroy(serverp);
    return -1;
  }
}

int serverset (server* serverp, int port, size_t bps)
{
  assert(serverp != NULL);
  
  FD_CLR(serverp->fd, &(serverp->fdset));
  
  sockclose(serverp->fd);
  
  serverp->fd = 0;
  while(serverp->fd <= 0)
    serverp->fd = socklisten(port, BACKLOG, SOCKET_TCP);
  
  if (serverp->fd > serverp->fdmax)
    serverp->fdmax = serverp->fd +1;
  
  FD_SET(serverp->fd, &(serverp->fdset));
  
  serverp->bps = bps;
  
  return 0;
}


int serverinit(server *serverp, int port, size_t bps)
{
  assert(serverp != NULL);
  memset(serverp, 0, sizeof(*serverp));
  
  FD_ZERO(&(serverp->fdset));
  FD_ZERO(&(serverp->readset));
  
  while(serverp->fd <= 0)
    serverp->fd = socklisten(port, BACKLOG, SOCKET_TCP);
  
  FD_SET(serverp->fd, &(serverp->fdset));
  serverp->fdmax = serverp->fd + 1;
  serverp->bps = bps;
//   clock_gettime(CLOCK_MONOTONIC, &(serverp->mark));
  
  serverp->update = client_update;
  serverp->quit = client_quit;
  serverp->frame = 0;
  
  return 0;
}

/* dropa um cliente da lista, retorna clientp->next */
client * drop(server *serverp, client * clientp)
{
  client * c = clientp;
  
#ifdef USE_THREADS
//   printf("%s %d unlock mutex %x\n", __func__, __LINE__, &(c->lock));
  pthread_mutex_unlock(&(c->lock));
#endif
  
  client_destroy(serverp, c);
  close(c->id);
  FD_CLR(c->id, &(serverp->fdset));
  
  if (clientp->prev) 
    clientp->prev->next = clientp->next;
  if (clientp->next) 
    clientp->next->prev = clientp->prev;
  
  if (serverp->clients == clientp)
    serverp->clients = clientp->next;
  
  /* walks forward */
  clientp = clientp->next;
  
  free(c);
  
  return clientp;
}

int serverdestroy(server *serverp)
{
  client *clientp = 0;

  /* libera a lista de clientes */
  while(serverp->clients != 0)
  {
    clientp = serverp->clients->next;
    client_destroy(serverp, serverp->clients);
    close(serverp->clients->id);
    free(serverp->clients);
    serverp->clients = clientp;
  }
  
  return 0;
}

void client_create(client * clientp)
{
  clientp->inputsz = clientp->outputsz = CLIENT_BUFSZ;
  clientp->input = calloc(CLIENT_BUFSZ, 1);
  clientp->output = calloc(CLIENT_BUFSZ, 1);
  clientp->outputlen = clientp->inputlen = 0;
  
  #ifdef USE_THREADS
  pthread_mutex_init(&(clientp->lock), NULL);
  #endif
}

/* destroi a memoria interna de um cliente e dropa ele
 * da lista */
void client_destroy(server * s, client * clientp)
{
  if (s->quit != 0)
    s->quit(clientp);
  
  #ifdef USE_THREADS
  pthread_mutex_destroy(&(clientp->lock));
  #endif
  
  free(clientp->input);
  free(clientp->output);
}

int client_quit(client * clientp)
{
  assert(clientp != NULL);
  return 0;
}

int client_update(client * clientp)
{
  assert(clientp != NULL);
  if (((char*)clientp->input)[0] == 'q')
    return 1;
  clientp->inputlen = 0;
  return 0;
}
